1 /* 2 * Collie - An asynchronous event-driven network framework using Dlang development 3 * 4 * Copyright (C) 2015-2017 Shanghai Putao Technology Co., Ltd 5 * 6 * Developer: putao's Dlang team 7 * 8 * Licensed under the Apache-2.0 License. 9 * 10 */ 11 module collie.codec.http.server.httpform; 12 13 import kiss.container.ByteBuffer; 14 import std.array; 15 import std.string; 16 import std.exception; 17 import std.algorithm.searching : canFind, countUntil; 18 import kiss.logger; 19 import collie.utils.string; 20 import kiss.container.Vector; 21 import std.experimental.allocator.mallocator; 22 import std.uri; 23 24 class HTTPFormException : Exception 25 { 26 mixin basicExceptionCtors; 27 } 28 29 30 alias HttpForm = HTTPForm; 31 32 class HTTPForm 33 { 34 import std.experimental.allocator.mallocator; 35 alias TBuffer = Vector!(ubyte,Mallocator,false); 36 alias StringArray = string[]; 37 enum ubyte[2] ENDMYITLFORM = ['-','-']; 38 enum ubyte[2] LRLN = ['\r','\n']; 39 40 final class FormFile 41 { 42 @property fileName() const {return _fileName;} 43 @property contentType() const {return _contentType;} 44 @property fileSize()const {return _length;} 45 void read(size_t size,scope void delegate(in ubyte[] data) cback) 46 { 47 size = size > _length ? _length : size; 48 _body.rest(_startSize); 49 _body.read(size,cback); 50 } 51 private : 52 Buffer _body; 53 size_t _startSize = 0; 54 size_t _length = 0; 55 string _fileName; 56 string _contentType; 57 this(){} 58 } 59 60 this(string contype, Buffer body_) 61 { 62 // logDebug("contype is : ", contype); 63 if (canFind(contype, "multipart/form-data")) 64 { 65 string strBoundary; 66 splitNameValue(contype,';','=',(string key,string value){ 67 if(isSameIngnoreLowUp(strip(key),"boundary")) { 68 strBoundary = value.idup; 69 return false; 70 } 71 return true; 72 }); 73 logDebug("strBoundary : ", strBoundary); 74 if (strBoundary.length > 0) 75 { 76 if(strBoundary[0] == '\"') 77 strBoundary = strBoundary[1..strBoundary.length -1]; 78 readMultiFrom(strBoundary, body_); 79 } 80 } 81 else if (canFind(contype, "application/x-www-form-urlencoded")) 82 { 83 readXform(body_); 84 } 85 else 86 { 87 _vaild = false; 88 } 89 body_.rest(); 90 } 91 92 @property bool isVaild() const 93 { 94 return _vaild; 95 } 96 97 /** 98 * Request body parameters ($_POST). 99 * 100 */ 101 @property string[string] formData() 102 { 103 return _formData; 104 } 105 106 @property void formData(string[string] v) 107 { 108 _formData = v; 109 } 110 111 protected string[string] _formData; 112 113 114 @property StringArray[string] formMap() 115 { 116 return _forms; 117 } 118 119 120 @property FormFile[string] fileMap() 121 { 122 return _files; 123 } 124 125 string[] fileKeys() 126 { 127 return _files.keys(); 128 } 129 130 string getFromValue(string key) 131 { 132 StringArray aty = _forms.get(key, StringArray.init); 133 if(aty.length == 0) 134 return ""; 135 else 136 return aty[0]; 137 } 138 139 StringArray getFromValueArray(string key) 140 { 141 StringArray aty; 142 return _forms.get(key, aty); 143 } 144 145 FormFile getFileValue(string key) 146 { 147 return _files.get(key, null); 148 } 149 150 protected: 151 void readXform(Buffer buffer) 152 { 153 buffer.rest(0); 154 ubyte[] str; 155 buffer.readAll((in ubyte[] data){ 156 str ~= data; 157 }); 158 splitNameValue(cast(string)str,'&','=',(string key, string value){ 159 value = value.replace("+","%20"); 160 string v = decodeComponent(value); 161 logDebugf("recv: %s=%s, decoded:%s",key, value, v); 162 string k = key.idup; 163 _formData[k] = v; 164 if(value.length > 0) 165 _forms[k] ~= v; 166 else 167 _forms[k] ~= ""; 168 return true; 169 }); 170 } 171 172 void readMultiFrom(string brand, Buffer buffer) 173 { 174 // buffer.readAll((in ubyte[] data){ 175 // logDebug("data is : ", cast(string)data); 176 // }); 177 // logDebug("................."); 178 buffer.rest(0); 179 string brony = "--"; 180 brony ~= brand; 181 string str; 182 auto buf = Vector!(ubyte,Mallocator)(); 183 do{ 184 //Appender!(ubyte[]) buf = appender!(ubyte[]); 185 buf.clear(); 186 buffer.readLine((in ubyte[] data){ 187 logDebug("data is : ", cast(string)data); 188 buf.insertBack(data); 189 //buf.put(data); 190 }); 191 auto sttr = cast(string)buf.data(); 192 str = sttr.strip; 193 if(str.length == 0){ 194 continue; 195 } else if(str == brony){ 196 break; 197 } else { 198 return; 199 } 200 } while(true); 201 logDebug("read to : ", buffer.readPos); 202 logDebug("brony length : ", brony.length); 203 brony = "\r\n" ~ brony; 204 bool run; 205 do 206 { 207 run = readMultiftomPart(buffer, cast(ubyte[]) brony); 208 } 209 while (run); 210 } 211 212 bool readMultiftomPart(Buffer buffer, ubyte[] boundary) 213 { 214 auto buf = Vector!(ubyte,Mallocator)(); 215 string cd; 216 string cType; 217 do { 218 buf.clear(); 219 buffer.readLine((in ubyte[] data){ 220 buf.insertBack(data); 221 }); 222 auto line = buf.data(); 223 logDebug(cast(string)line); 224 if(line.length == 0) 225 break; 226 auto pos = countUntil(line, cast(ubyte)':') ; // (cast(string) line).indexOf(":"); 227 ++pos; 228 if (pos <= 0 || pos >= line.length) 229 continue; 230 string key = cast(string)(line[0 .. pos - 1]); 231 if(isSameIngnoreLowUp(strip(key),"content-disposition")){ 232 line = line[pos .. $]; 233 pos = countUntil(line, cast(ubyte)';'); 234 ++pos; 235 if (pos <= 0 || pos >= line.length) 236 continue; 237 cd = cast(string)line[pos + 1 .. $].idup; 238 } else if(isSameIngnoreLowUp(strip(key),"content-type")){ 239 cType = strip((cast(string)(line[pos + 1 .. $]))).idup; 240 } 241 } while(true); 242 if (cd.length == 0) 243 return false; 244 245 string name; 246 string filename; 247 logDebug("cd ==== ", cd); 248 splitNameValue(cd, ';' , '=' , (string key, string value){ 249 logDebug("key : ", key, " value: ", value); 250 string tkey = strip(key); 251 //string tvalue = strip(value); 252 string handleValue(string rv){ 253 if(rv.length > 0) 254 if(rv[0] == '\"') rv = rv[1 .. $]; 255 if(rv.length > 0) 256 if(rv[$-1] == '\"') rv = rv[0 .. $ - 1]; 257 return rv.idup; 258 } 259 switch(tkey){ 260 case "name": 261 name = handleValue(strip(value)); 262 break; 263 case "filename": 264 filename = handleValue(strip(value)); 265 break; 266 default: 267 break; 268 } 269 return true; 270 }); 271 if (filename.length > 0) 272 { 273 import std.array; 274 FormFile fp = new FormFile; 275 fp._fileName = filename; 276 fp._contentType = cType; 277 fp._startSize = buffer.readPos(); 278 fp._body = buffer; 279 buffer.readUtil(boundary,(in ubyte[] rdata) { 280 fp._length += rdata.length; 281 }); 282 _files[name] = fp; 283 } 284 else 285 { 286 import std.array; 287 auto value = appender!(string)(); 288 buffer.readUtil(boundary, delegate(in ubyte[] rdata) { 289 value.put(cast(string) rdata); 290 }); 291 string stdr = value.data; 292 logDebug("name == ", name); 293 logDebug("value == ", stdr); 294 295 _forms[name] ~= stdr; 296 logDebug("form : ", _forms); 297 298 } 299 ubyte[2] ub; 300 bool frist = true; 301 buffer.read(2,(in ubyte[] dt){ 302 switch(dt.length){ 303 case 2: 304 ub[] = dt[]; 305 break; 306 case 1:{ 307 if(frist){ 308 ub[0] = dt[0]; 309 frist = false; 310 } else { 311 ub[1] = dt[0]; 312 } 313 }break; 314 default: 315 break; 316 } 317 }); 318 if (ub == ENDMYITLFORM) 319 { 320 return false; 321 } 322 enforce!HTTPFormException(ub == LRLN, "showed be \\r\\n"); 323 return true; 324 } 325 326 private: 327 bool _vaild = true; 328 StringArray[string] _forms; 329 FormFile[string] _files; 330 }